/*
 * Copyright Paul James Mutton, 2001-2007, http://www.jibble.org/
 * This file is part of PircBot.
 * This software is dual-licensed, allowing you to choose between the GNU
 * General Public License (GPL) and the www.jibble.org Commercial License.
 * Since the GPL may be too restrictive for use in a proprietary application,
 * a commercial license is also provided. Full license information can be
 * found at http://www.jibble.org/licenses/
 */

package org.jibble.pircbot;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;

import main.Bot;
import main.Main;

import org.apache.log4j.Logger;

import output.OutputQueue;

/**
 * PircBot is a Java framework for writing IRC bots quickly and easily.
 * <p>
 * It provides an event-driven architecture to handle common IRC events, flood protection, DCC support, ident support, and more.
 * The comprehensive logfile format is suitable for use with pisg to generate channel statistics.
 * <p>
 * Methods of the PircBot class can be called to send events to the IRC server that it connects to. For example, calling the
 * sendMessage method will send a message to a channel or user on the IRC server. Multiple servers can be supported using multiple
 * instances of PircBot.
 * <p>
 * To perform an action when the PircBot receives a normal message from the IRC server, you would override the onMessage method
 * defined in the PircBot class. All on<i>XYZ</i> methods in the PircBot class are automatically called when the event <i>XYZ</i>
 * happens, so you would override these if you wish to do something when it does happen.
 * <p>
 * Some event methods, such as onPing, should only really perform a specific function (i.e. respond to a PING from the server).
 * For your convenience, such methods are already correctly implemented in the PircBot and should not normally need to be
 * overridden. Please read the full documentation for each method to see which ones are already implemented by the PircBot class.
 * <p>
 * Please visit the PircBot homepage at <a href="http://www.jibble.org/pircbot.php" >http://www.jibble.org/pircbot.php</a> for
 * full revision history, a beginners guide to creating your first PircBot and a list of some existing Java IRC bots and clients
 * that use the PircBot framework.
 * 
 * @author Paul James Mutton, <a href="http://www.jibble.org/">http://www.jibble.org/</a>
 * @version 1.4.6 (Build time: Wed Apr 11 19:20:59 2007)
 */
public class PircBot extends ReplyConstants {

   /**
    * The definitive version number of this release of PircBot. (Note: Change this before automatically building releases)
    */
   public static final String VERSION = "1.4.6";

   private static final int OP_ADD = 1;
   private static final int OP_REMOVE = 2;
   private static final int VOICE_ADD = 3;
   private static final int VOICE_REMOVE = 4;

   /**
    * Returns the number of milliseconds that will be used to separate consecutive messages to the server from the outgoing
    * message queue.
    * 
    * @return Number of milliseconds.
    */
   public static final long getMessageDelay() {
	  return Main.getBotSettings().getIrcDelay();
   }

   /**
    * Sets the number of milliseconds to delay between consecutive messages when there are multiple messages waiting in the
    * outgoing message queue. This has a default value of 1000ms. It is a good idea to stick to this default value, as it will
    * prevent your bot from spamming servers and facing the subsequent wrath! However, if you do need to change this delay value
    * (<b>not recommended</b>), then this is the method to use.
    * 
    * @param delay
    *           The number of milliseconds between each outgoing message.
    */
   public static final void setMessageDelay(final long delay) {
	  if (delay < 0)
		 throw new IllegalArgumentException("Cannot have a negative time.");
	  _messageDelay = delay;
   }

   // Connection stuff.
   private InputThread _inputThread = null;

   private OutputThread _outputThread;

   private String _charset = null;

   private InetAddress _inetAddress = null;

   // Details about the last server that we connected to.
   private String _server = null;

   private int _port = -1;

   private String _password = null;

   // detta
   private static long _messageDelay = 500;

   // A Hashtable of channels that points to a selfreferential Hashtable of
   // User objects (used to remember which users are in which channels).
   private Hashtable<String, Hashtable<User, User>> _channels = new Hashtable<String, Hashtable<User, User>>();

   // A Hashtable to temporarily store channel topics when we join them
   // until we find out who set that topic.
   private final Hashtable<String, String> _topics = new Hashtable<String, String>();

   // DccManager to process and handle all DCC events.
   private final DccManager _dccManager = new DccManager(this);

   private int[] _dccPorts = null;

   private InetAddress _dccInetAddress = null;

   // Default settings for the PircBot.
   private boolean _autoNickChange = false;

   private boolean _verbose = false;

   private String _name = "LucidBot";

   private String _nick = _name;

   private String _login = "LucidBot";

   private String _version = "LucidBot";

   private String _finger = "You ought to be arrested for fingering a bot!";

   private final static String _channelPrefixes = "#&+!";

   /**
    * Add a user to the specified channel in our memory. Overwrite the existing entry if it exists.
    */
   private final void addUser(final String channel, final User user) {
	  synchronized (_channels) {
		 Hashtable<User, User> users = _channels.get(channel.toLowerCase());
		 if (users == null) {
			users = new Hashtable<User, User>();
			_channels.put(channel.toLowerCase(), users);
		 }
		 users.put(user, user);
	  }
   }

   /**
    * Bans a user from a channel. An example of a valid hostmask is "*!*compu@*.18hp.net". This may be used in conjunction with
    * the kick method to permanently remove a user from a channel. Successful use of this method may require the bot to have
    * operator status itself.
    * 
    * @param channel
    *           The channel to ban the user from.
    * @param hostmask
    *           A hostmask representing the user we're banning.
    */
   public final void ban(final String channel, final String hostmask) {
	  sendRawLine("MODE " + channel + " +b " + hostmask);
   }

   /**
    * Attempt to change the current nick (nickname) of the bot when it is connected to an IRC server. After confirmation of a
    * successful nick change, the getNick method will return the new nick.
    * 
    * @param newNick
    *           The new nick to use.
    */
   public final void changeNick(final String newNick) {
	  sendRawLine("NICK " + newNick);
   }

   /**
    * Attempt to connect to the specified IRC server. The onConnect method is called upon success.
    * 
    * @param hostname
    *           The hostname of the server to connect to.
    * @throws IOException
    *            if it was not possible to connect to the server.
    * @throws IrcException
    *            if the server would not let us join it.
    * @throws NickAlreadyInUseException
    *            if our nick is already in use on the server.
    * @throws AlreadyConnectedException
    */
   public final void connect(final String hostname) throws IOException, IrcException, NickAlreadyInUseException,
		 AlreadyConnectedException {
	  synchronized (this) {
		 this.connect(hostname, Main.getBotSettings().getIrcPort(), null);
	  }
   }

   /**
    * Attempt to connect to the specified IRC server and port number. The onConnect method is called upon success.
    * 
    * @param hostname
    *           The hostname of the server to connect to.
    * @param port
    *           The port number to connect to on the server.
    * @throws IOException
    *            if it was not possible to connect to the server.
    * @throws IrcException
    *            if the server would not let us join it.
    * @throws NickAlreadyInUseException
    *            if our nick is already in use on the server.
    * @throws AlreadyConnectedException
    */
   public final void connect(final String hostname, final int port) throws IOException, IrcException, NickAlreadyInUseException,
		 AlreadyConnectedException {
	  synchronized (this) {
		 this.connect(hostname, port, null);
	  }
   }

   /**
    * Attempt to connect to the specified IRC server using the supplied password. The onConnect method is called upon success.
    * 
    * @param hostname
    *           The hostname of the server to connect to.
    * @param port
    *           The port number to connect to on the server.
    * @param password
    *           The password to use to join the server.
    * @throws IOException
    *            if it was not possible to connect to the server.
    * @throws IrcException
    *            if the server would not let us join it.
    * @throws NickAlreadyInUseException
    *            if our nick is already in use on the server.
    */
   public final void connect(final String hostname, final int port, final String password) throws IOException, IrcException,
		 NickAlreadyInUseException, AlreadyConnectedException {
	  synchronized (this) {
		 try {
			_server = hostname;
			_port = port;
			_password = password;

			if (isConnected())
			   throw new AlreadyConnectedException("The PircBot is already connected to an IRC server.  Disconnect first.");

			// Now start the outputThread that will be used to send all
			// messages.
			if (_outputThread == null) {
			   _outputThread = new OutputThread(this, OutputQueue.INSTANCE, getNick());
			   _outputThread.start();
			}

			// Don't clear the outqueue - there might be something important
			// in
			// it!

			// Clear everything we may have know about channels.
			removeAllChannels();

			// Connect to the server.
			final Socket socket = new Socket(hostname, port);
			log("*** Connected to server.");

			_inetAddress = socket.getLocalAddress();

			InputStreamReader inputStreamReader = null;
			OutputStreamWriter outputStreamWriter = null;
			if (getEncoding() == null) {
			   // Otherwise, just use the JVM's default encoding.
			   inputStreamReader = new InputStreamReader(socket.getInputStream());
			   outputStreamWriter = new OutputStreamWriter(socket.getOutputStream());
			} else {
			   // Assume the specified encoding is valid for this JVM.
			   inputStreamReader = new InputStreamReader(socket.getInputStream(), getEncoding());
			   outputStreamWriter = new OutputStreamWriter(socket.getOutputStream(), getEncoding());
			}

			final BufferedReader breader = new BufferedReader(inputStreamReader);
			final BufferedWriter bwriter = new BufferedWriter(outputStreamWriter);

			// Attempt to join the server.
			if (password != null && !password.equals("")) {
			   OutputThread.sendRawLine(this, bwriter, "PASS " + password);
			}
			String nick = getName();
			OutputThread.sendRawLine(this, bwriter, "NICK " + nick);
			OutputThread.sendRawLine(this, bwriter, "USER " + getLogin() + " 8 * :" + getVersion());

			_inputThread = new InputThread(this, socket, breader, bwriter);

			// Read stuff back from the server to see if we connected.
			String line = null;
			int tries = 1;
			while ((line = breader.readLine()) != null) {

			   handleLine(line);

			   final int firstSpace = line.indexOf(' ');
			   final int secondSpace = line.indexOf(" ", firstSpace + 1);
			   if (secondSpace >= 0) {
				  final String code = line.substring(firstSpace + 1, secondSpace);

				  if ("004".equals(code)) {
					 // We're connected to the server.
					 break;
				  } else if ("433".equals(code)) {
					 if (_autoNickChange) {
						tries++;
						nick = getName() + tries;
						OutputThread.sendRawLine(this, bwriter, "NICK " + nick);
					 } else {
						socket.close();
						_inputThread = null;
						throw new NickAlreadyInUseException(line);
					 }
				  } else if (code.charAt(0) == '5' || code.charAt(0) == '4') {
					 socket.close();
					 _inputThread = null;
					 throw new IrcException("Could not log into the IRC server: " + line);
				  }
			   }
			   setNick(nick);

			}

			log("*** Logged onto server.");

			// This makes the socket timeout on read operations after 5
			// minutes.
			// Maybe in some future version I will let the user change this
			// at
			// runtime.
			socket.setSoTimeout(5 * 60 * 1000);

			// Now start the InputThread to read all other lines from the
			// server.
			_inputThread.start();

			onConnect();
		 } catch (Exception e) {
			Logger.getLogger("Error").error("", e);
			_inputThread = null;
			throw new IOException("Socket is closed");
		 }
	  }
   }

   /**
    * Attempts to accept a DCC CHAT request by a client. Please use the onIncomingChatRequest method to receive files.
    * 
    * @deprecated As of PircBot 1.2.0, use {@link #onIncomingChatRequest(DccChat)}
    */
   @Deprecated
   protected final DccChat dccAcceptChatRequest(final String sourceNick, final long address, final int port) {
	  throw new RuntimeException("dccAcceptChatRequest is deprecated, please use onIncomingChatRequest");
   }

   /**
    * Receives a file that is being sent to us by a DCC SEND request. Please use the onIncomingFileTransfer method to receive
    * files.
    * 
    * @deprecated As of PircBot 1.2.0, use {@link #onIncomingFileTransfer(DccFileTransfer)}
    */
   @Deprecated
   protected final void dccReceiveFile(final File file, final long address, final int port, final int size) {
	  throw new RuntimeException("dccReceiveFile is deprecated, please use sendFile");
   }

   /**
    * Attempts to establish a DCC CHAT session with a client. This method issues the connection request to the client and then
    * waits for the client to respond. If the connection is successfully made, then a DccChat object is returned by this method.
    * If the connection is not made within the time limit specified by the timeout value, then null is returned.
    * <p>
    * It is <b>strongly recommended</b> that you call this method within a new Thread, as it may take a long time to return.
    * <p>
    * This method may not be overridden.
    * 
    * @since PircBot 0.9.8
    * @param nick
    *           The nick of the user we are trying to establish a chat with.
    * @param timeout
    *           The number of milliseconds to wait for the recipient to accept the chat connection (we recommend about 120000).
    * @return a DccChat object that can be used to send and recieve lines of text. Returns <b>null</b> if the connection could not
    *         be made.
    * @see DccChat
    */
   public final DccChat dccSendChatRequest(final String nick, final int timeout) {
	  DccChat chat = null;
	  try {
		 ServerSocket ss = null;

		 final int[] ports = getDccPorts();
		 if (ports == null) {
			// Use any free port.
			ss = new ServerSocket(0);
		 } else {
			for (int port : ports) {
			   try {
				  ss = new ServerSocket(port);
				  // Found a port number we could use.
				  break;
			   } catch (final Exception e) {
				  // Do nothing; go round and try another port.
			   }
			}
			if (ss == null)
			   // No ports could be used.
			   throw new IOException("All ports returned by getDccPorts() are in use.");
		 }

		 ss.setSoTimeout(timeout);
		 final int port = ss.getLocalPort();

		 InetAddress inetAddress = getDccInetAddress();
		 if (inetAddress == null) {
			inetAddress = getInetAddress();
		 }
		 final byte[] ip = inetAddress.getAddress();
		 final long ipNum = ipToLong(ip);

		 sendCTCPCommand(nick, "DCC CHAT chat " + ipNum + " " + port);

		 // The client may now connect to us to chat.
		 final Socket socket = ss.accept();

		 // Close the server socket now that we've finished with it.
		 ss.close();

		 chat = new DccChat(this, nick, socket);
	  } catch (final Exception e) {
		 // Do nothing.
	  }
	  return chat;
   }

   /**
    * Sends a file to another user. Resuming is supported. The other user must be able to connect directly to your bot to be able
    * to receive the file.
    * <p>
    * You may throttle the speed of this file transfer by calling the setPacketDelay method on the DccFileTransfer that is
    * returned.
    * <p>
    * This method may not be overridden.
    * 
    * @since 0.9c
    * @param file
    *           The file to send.
    * @param nick
    *           The user to whom the file is to be sent.
    * @param timeout
    *           The number of milliseconds to wait for the recipient to acccept the file (we recommend about 120000).
    * @return The DccFileTransfer that can be used to monitor this transfer.
    * @see DccFileTransfer
    */
   public final DccFileTransfer dccSendFile(final File file, final String nick, final int timeout) {
	  final DccFileTransfer transfer = new DccFileTransfer(this, _dccManager, file, nick, timeout);
	  transfer.doSend(true);
	  return transfer;
   }

   /**
    * Removes operator privilidges from a user on a channel. Successful use of this method may require the bot to have operator
    * status itself.
    * 
    * @param channel
    *           The channel we're deopping the user on.
    * @param nick
    *           The nick of the user we are deopping.
    */
   public final void deOp(final String channel, final String nick) {
	  setMode(channel, "-o " + nick);
   }

   /**
    * Removes voice privilidges from a user on a channel. Successful use of this method may require the bot to have operator
    * status itself.
    * 
    * @param channel
    *           The channel we're devoicing the user on.
    * @param nick
    *           The nick of the user we are devoicing.
    */
   public final void deVoice(final String channel, final String nick) {
	  setMode(channel, "-v " + nick);
   }

   /**
    * This method disconnects from the server cleanly by calling the quitServer() method. Providing the PircBot was connected to
    * an IRC server, the onDisconnect() will be called as soon as the disconnection is made by the server.
    * 
    * @see #quitServer() quitServer
    * @see #quitServer(String) quitServer
    */
   public void disconnect() {
   }

   /**
    * Disposes of all thread resources used by this PircBot. This may be useful when writing bots or clients that use multiple
    * servers (and therefore multiple PircBot instances) or when integrating a PircBot with an existing program.
    * <p>
    * Each PircBot runs its own threads for dispatching messages from its outgoing message queue and receiving messages from the
    * server. Calling dispose() ensures that these threads are stopped, thus freeing up system resources and allowing the PircBot
    * object to be garbage collected if there are no other references to it.
    * <p>
    * Once a PircBot object has been disposed, it should not be used again. Attempting to use a PircBot that has been disposed may
    * result in unpredictable behaviour.
    * 
    * @since 1.2.2
    */
   public void dispose() {
	  // System.out.println("disposing...");
	  synchronized (this) {
		 try {
			_outputThread.interrupt();
		 } catch (Exception e) {

		 }
		 try {
			_inputThread.dispose();
		 } catch (Exception e) {

		 }
	  }
   }

   /**
    * Returns true if and only if the object being compared is the exact same instance as this PircBot. This may be useful if you
    * are writing a multiple server IRC bot that uses more than one instance of PircBot.
    * 
    * @since PircBot 0.9.9
    * @return true if and only if Object o is a PircBot and equal to this.
    */

   @Override
   public boolean equals(final Object o) {
	  // This probably has the same effect as Object.equals, but that may
	  // change...
	  if (o instanceof PircBot) {
		 final PircBot other = (PircBot) o;
		 return other == this;
	  }
	  return false;
   }

   /**
    * Returns an array of all channels that we are in. Note that if you call this method immediately after joining a new channel,
    * the new channel may not appear in this array as it is not possible to tell if the join was successful until a response is
    * received from the IRC server.
    * 
    * @since PircBot 1.0.0
    * @return A String array containing the names of all channels that we are in.
    */
   public final String[] getChannels() {
	  String[] channels = new String[0];
	  synchronized (_channels) {
		 channels = new String[_channels.size()];
		 final Enumeration<String> enumeration = _channels.keys();
		 for (int i = 0; i < channels.length; i++) {
			channels[i] = enumeration.nextElement();
		 }
	  }
	  return channels;
   }

   /**
    * Returns the InetAddress used when sending DCC chat or file transfers. If this is null, the default InetAddress will be used.
    * 
    * @since PircBot 1.4.4
    * @return The current DCC InetAddress, or null if left as default.
    */
   public InetAddress getDccInetAddress() {
	  return _dccInetAddress;
   }

   /**
    * Returns the set of port numbers to be used when sending a DCC chat or file transfer. This is useful when you are behind a
    * firewall and need to set up port forwarding. The array of port numbers is traversed in sequence until a free port is found
    * to listen on. A DCC tranfer will fail if all ports are already in use. If set to null, <i>any</i> free port number will be
    * used.
    * 
    * @since PircBot 1.4.4
    * @return An array of port numbers that PircBot can use to send DCC transfers, or null if any port is allowed.
    */
   public int[] getDccPorts() {
	  if (_dccPorts == null || _dccPorts.length == 0)
		 return null;
	  // Clone the array to prevent external modification.
	  return _dccPorts.clone();
   }

   /**
    * Returns the encoding used to send and receive lines from the IRC server, or null if not set. Use the setEncoding method to
    * change the encoding charset.
    * 
    * @since PircBot 1.0.4
    * @return The encoding used to send outgoing messages, or null if not set.
    */
   public String getEncoding() {
	  return _charset;
   }

   /**
    * Gets the internal finger message of the PircBot.
    * 
    * @return The finger message of the PircBot.
    */
   public final String getFinger() {
	  return _finger;
   }

   /**
    * Returns the InetAddress used by the PircBot. This can be used to find the I.P. address from which the PircBot is connected
    * to a server.
    * 
    * @since PircBot 1.4.4
    * @return The current local InetAddress, or null if never connected.
    */
   public InetAddress getInetAddress() {
	  return _inetAddress;
   }

   /**
    * Gets the internal login of the PircBot.
    * 
    * @return The login of the PircBot.
    */
   public final String getLogin() {
	  return _login;
   }

   /**
    * Gets the maximum length of any line that is sent via the IRC protocol. The IRC RFC specifies that line lengths, including
    * the trailing \r\n must not exceed 512 bytes. Hence, there is currently no option to change this value in PircBot. All lines
    * greater than this length will be truncated before being sent to the IRC server.
    * 
    * @return The maximum line length (currently fixed at 512)
    */
   public final int getMaxLineLength() {
	  return InputThread.MAX_LINE_LENGTH;
   }

   /**
    * Gets the name of the PircBot. This is the name that will be used as as a nick when we try to join servers.
    * 
    * @return The name of the PircBot.
    */
   public final String getName() {
	  return _name;
   }

   /**
    * Returns the current nick of the bot. Note that if you have just changed your nick, this method will still return the old
    * nick until confirmation of the nick change is received from the server.
    * <p>
    * The nick returned by this method is maintained only by the PircBot class and is guaranteed to be correct in the context of
    * the IRC server.
    * 
    * @since PircBot 1.0.0
    * @return The current nick of the bot.
    */
   public String getNick() {
	  return _nick;
   }

   /**
    * Returns the last password that we used when connecting to an IRC server. This does not imply that the connection attempt to
    * the server was successful (we suggest you look at the onConnect method). A value of null is returned if the PircBot has
    * never tried to connect to a server using a password.
    * 
    * @since PircBot 0.9.9
    * @return The last password that we used when connecting to an IRC server. Returns null if we have not previously connected
    *         using a password.
    */
   public final String getPassword() {
	  return _password;
   }

   /**
    * Returns the port number of the last IRC server that the PircBot tried to connect to. This does not imply that the connection
    * attempt to the server was successful (we suggest you look at the onConnect method). A value of -1 is returned if the PircBot
    * has never tried to connect to a server.
    * 
    * @since PircBot 0.9.9
    * @return The port number of the last IRC server we connected to. Returns -1 if no connection attempts have ever been made.
    */
   public final int getPort() {
	  return _port;
   }

   /**
    * Returns the name of the last IRC server the PircBot tried to connect to. This does not imply that the connection attempt to
    * the server was successful (we suggest you look at the onConnect method). A value of null is returned if the PircBot has
    * never tried to connect to a server.
    * 
    * @return The name of the last machine we tried to connect to. Returns null if no connection attempts have ever been made.
    */
   public final String getServer() {
	  return _server;
   }

   /**
    * Returns an array of all users in the specified channel.
    * <p>
    * There are some important things to note about this method:-
    * <ul>
    * <li>This method may not return a full list of users if you call it before the complete nick list has arrived from the IRC
    * server.</li>
    * <li>If you wish to find out which users are in a channel as soon as you join it, then you should override the onUserList
    * method instead of calling this method, as the onUserList method is only called as soon as the full user list has been
    * received.</li>
    * <li>This method will return immediately, as it does not require any interaction with the IRC server.</li>
    * <li>The bot must be in a channel to be able to know which users are in it.</li>
    * </ul>
    * 
    * @since PircBot 1.0.0
    * @param channel
    *           The name of the channel to list.
    * @return An array of User objects. This array is empty if we are not in the channel.
    * @see #onUserList(String,User[]) onUserList
    */
   public final User[] getUsers(final String channel) {
	  User[] userArray = new User[0];
	  synchronized (userArray) {
		 final Hashtable<User, User> users = _channels.get(channel.toLowerCase());
		 if (users != null) {
			userArray = new User[users.size()];
			final Enumeration<User> enumeration = users.elements();
			for (int i = 0; i < userArray.length; i++) {
			   final User user = enumeration.nextElement();
			   userArray[i] = user;
			}
		 }
	  }
	  return userArray;
   }

   /**
    * Gets the internal version of the PircBot.
    * 
    * @return The version of the PircBot.
    */
   public final String getVersion() {
	  return _version;
   }

   /**
    * This method handles events when any line of text arrives from the server, then calling the appropriate method in the
    * PircBot. This method is protected and only called by the InputThread for this instance.
    * <p>
    * This method may not be overridden!
    * 
    * @param line
    *           The raw line of text from the server.
    */
   protected void handleLine(final String line) {
	  try {
		 log(line);

		 // Check for server pings.
		 if (line.startsWith("PING ")) {
			// Respond to the ping and return immediately.
			onServerPing(line.substring(5));
			return;
		 }

		 String sourceNick = "";
		 String sourceLogin = "";
		 String sourceHostname = "";

		 StringTokenizer tokenizer = new StringTokenizer(line);
		 final String senderInfo = tokenizer.nextToken();
		 String command = tokenizer.nextToken();
		 String target = null;

		 final int exclamation = senderInfo.indexOf('!');
		 final int at = senderInfo.indexOf('@');
		 if (senderInfo.charAt(0) == ':') {
			if (exclamation > 0 && at > 0 && exclamation < at) {
			   sourceNick = senderInfo.substring(1, exclamation);
			   sourceLogin = senderInfo.substring(exclamation + 1, at);
			   sourceHostname = senderInfo.substring(at + 1);
			} else {

			   if (tokenizer.hasMoreTokens()) {
				  final String token = command;

				  int code = -1;
				  try {
					 code = Integer.parseInt(token);
				  } catch (final NumberFormatException e) {
					 // Keep the existing value.
				  }

				  if (code == -1) {
					 // This is not a server response.
					 // It must be a nick without login and hostname.
					 // (or maybe a NOTICE or suchlike from the server)
					 sourceNick = senderInfo;
					 target = token;
				  } else {
					 final String errorStr = token;
					 final String response = line.substring(line.indexOf(errorStr, senderInfo.length()) + 4, line.length());
					 processServerResponse(code, response);
					 // Return from the method.
					 return;
				  }
			   } else {
				  // We don't know what this line means.
				  onUnknown(line);
				  // Return from the method;
				  return;
			   }

			}
		 }

		 command = command.toUpperCase();
		 if (sourceNick.charAt(0) == ':') {
			sourceNick = sourceNick.substring(1);
		 }
		 if (target == null) {
			target = tokenizer.nextToken();
		 }
		 if (target.charAt(0) == ':') {
			target = target.substring(1);
		 }

		 // Check for CTCP requests.
		 if ("PRIVMSG".equals(command) && line.indexOf(":\u0001") > 0 && line.endsWith("\u0001")) {
			final String request = line.substring(line.indexOf(":\u0001") + 2, line.length() - 1);
			if ("VERSION".equals(request)) {
			   // VERSION request
			   onVersion(sourceNick, sourceLogin, sourceHostname, target);
			} else if ("ACTION ".startsWith(request)) {
			   // ACTION request
			   onAction(sourceNick, sourceLogin, sourceHostname, target, request.substring(7));
			} else if (request.matches(".*PING [0-9]{1,}")) {
			   // PING request
			   onPing(sourceNick, sourceLogin, sourceHostname, target, request.replaceAll("[^0-9]", ""));
			} else if ("TIME".equals(request)) {
			   // TIME request
			   onTime(sourceNick, sourceLogin, sourceHostname, target);
			} else if ("FINGER".equals(request)) {
			   // FINGER request
			   onFinger(sourceNick, sourceLogin, sourceHostname, target);
			} else if ((tokenizer = new StringTokenizer(request)).countTokens() >= 5 && "DCC".equals(tokenizer.nextToken())) {
			   // This is a DCC request.
			   final boolean success = _dccManager.processRequest(sourceNick, sourceLogin, sourceHostname, request);
			   if (!success) {
				  // The DccManager didn't know what to do with the line.
				  onUnknown(line);
			   }
			} else {
			   // An unknown CTCP message - ignore it.
			   onUnknown(line);
			}
		 } else if ("PRIVMSG".equals(command) && _channelPrefixes.indexOf(target.charAt(0)) >= 0) {
			// This is a normal message to a channel.
			onMessage(target, sourceNick, "", line.substring(line.indexOf(" :") + 2));
		 } else if ("PRIVMSG".equals(command)) {
			// This is a private message to us.
			onPrivateMessage(sourceNick, "", line.substring(line.indexOf(" :") + 2));
		 } else if ("JOIN".equals(command)) {
			// Someone is joining a channel.
			final String channel = target;
			addUser(channel, new User("", sourceNick));
			onJoin(channel, sourceNick, sourceLogin, sourceHostname);
		 } else if ("PART".equals(command)) {
			// Someone is parting from a channel.
			this.removeUser(target, sourceNick);
			if (sourceNick.equals(getNick())) {
			   removeChannel(target);
			}
			onPart(target, sourceNick, sourceLogin, sourceHostname);
		 } else if ("NICK".equals(command)) {
			// Somebody is changing their nick.
			final String newNick = target;
			renameUser(sourceNick, newNick);
			if (sourceNick.equals(getNick())) {
			   // Update our nick if it was us that changed nick.
			   setNick(newNick);
			}
			onNickChange(sourceNick, sourceLogin, sourceHostname, newNick);
		 } else if ("NOTICE".equals(command)) {
			// Someone is sending a notice.
			onNotice(sourceNick, sourceLogin, sourceHostname, target, line.substring(line.indexOf(" :") + 2));
		 } else if ("QUIT".equals(command)) {
			// Someone has quit from the IRC server.
			if (sourceNick.equals(getNick())) {
			   removeAllChannels();
			} else {
			   this.removeUser(sourceNick);
			}
			onQuit(sourceNick, sourceLogin, sourceHostname, line.substring(line.indexOf(" :") + 2));
		 } else if ("KICK".equals(command)) {
			// Somebody has been kicked from a channel.
			final String recipient = tokenizer.nextToken();
			if (recipient.equals(getNick())) {
			   removeChannel(target);
			}
			this.removeUser(target, recipient);
			onKick(target, sourceNick, sourceLogin, sourceHostname, recipient, line.substring(line.indexOf(" :") + 2));
		 } else if ("MODE".equals(command)) {
			// Somebody is changing the mode on a channel or user.
			String mode = line.substring(line.indexOf(target, 2) + target.length() + 1);
			if (mode.charAt(0) == ':') {
			   mode = mode.substring(1);
			}
			processMode(target, sourceNick, sourceLogin, sourceHostname, mode);
		 } else if ("TOPIC".equals(command)) {
			// Someone is changing the topic.
			onTopic(target, line.substring(line.indexOf(" :") + 2), sourceNick, System.currentTimeMillis(), true);
		 } else if ("INVITE".equals(command)) {
			// Somebody is inviting somebody else into a channel.
			onInvite(target, sourceNick, sourceLogin, sourceHostname, line.substring(line.indexOf(" :") + 2));
		 } else {
			// If we reach this point, then we've found something that the
			// PircBot
			// Doesn't currently deal with.
			onUnknown(line);
		 }
	  } catch (Exception e) {
		 // the server probably kicked for more than 3 instances
		 // this is here just so the bot doesn't crash when that happens
	  }
   }

   /**
    * Returns the hashCode of this PircBot. This method can be called by hashed collection classes and is useful for managing
    * multiple instances of PircBots in such collections.
    * 
    * @since PircBot 0.9.9
    * @return the hash code for this instance of PircBot.
    */

   @Override
   public int hashCode() {
	  return super.hashCode();
   }

   /**
    * Identify the bot with NickServ, supplying the appropriate password. Some IRC Networks (such as freenode) require users to
    * <i>register</i> and <i>identify</i> with NickServ before they are able to send private messages to other users, thus
    * reducing the amount of spam. If you are using an IRC network where this kind of policy is enforced, you will need to make
    * your bot <i>identify</i> itself to NickServ before you can send private messages. Assuming you have already registered your
    * bot's nick with NickServ, this method can be used to <i>identify</i> with the supplied password. It usually makes sense to
    * identify with NickServ immediately after connecting to a server.
    * <p>
    * This method issues a raw NICKSERV command to the server, and is therefore safer than the alternative approach of sending a
    * private message to NickServ. The latter approach is considered dangerous, as it may cause you to inadvertently transmit your
    * password to an untrusted party if you connect to a network which does not run a NickServ service and where the untrusted
    * party has assumed the nick "NickServ". However, if your IRC network is only compatible with the private message approach,
    * you may typically identify like so:
    * 
    * <pre>
    * sendMessage(&quot;NickServ&quot;, &quot;identify PASSWORD&quot;);
    * </pre>
    * 
    * @param password
    *           The password which will be used to identify with NickServ.
    */
   public final void identify(final String password) {
	  sendRawLine("NICKSERV IDENTIFY " + password);
   }

   /**
    * A convenient method that accepts an IP address represented by a byte[] of size 4 and returns this as a long representation
    * of the same IP address.
    * 
    * @since PircBot 0.9.4
    * @param address
    *           the byte[] of size 4 representing the IP address.
    * @return a long representation of the IP address.
    */
   public long ipToLong(final byte[] address) {
	  if (address.length != 4)
		 throw new IllegalArgumentException("byte array must be of length 4");
	  long ipNum = 0;
	  long multiplier = 1;
	  for (int i = 3; i >= 0; i--) {
		 final int byteVal = (address[i] + 256) % 256;
		 ipNum += byteVal * multiplier;
		 multiplier *= 256;
	  }
	  return ipNum;
   }

   /**
    * Returns whether or not the PircBot is currently connected to a server. The result of this method should only act as a rough
    * guide, as the result may not be valid by the time you act upon it.
    * 
    * @return True if and only if the PircBot is currently connected to a server.
    */
   public final boolean isConnected() {
	  synchronized (this) {
		 return _inputThread != null && _inputThread.isConnected();
	  }
   }

   /**
    * Joins a channel.
    * 
    * @param channel
    *           The name of the channel to join (eg "#cs").
    */
   public final void joinChannel(final String channel) {
	  sendRawLine("JOIN " + channel);
   }

   /**
    * Joins a channel with a key.
    * 
    * @param channel
    *           The name of the channel to join (eg "#cs").
    * @param key
    *           The key that will be used to join the channel.
    */
   public final void joinChannel(final String channel, final String key) {
	  this.joinChannel(channel + " " + key);
   }

   /**
    * Kicks a user from a channel. This method attempts to kick a user from a channel and may require the bot to have operator
    * status in the channel.
    * 
    * @param channel
    *           The channel to kick the user from.
    * @param nick
    *           The nick of the user to kick.
    */
   public final void kick(final String channel, final String nick) {
	  this.kick(channel, nick, "");
   }

   /**
    * Kicks a user from a channel, giving a reason. This method attempts to kick a user from a channel and may require the bot to
    * have operator status in the channel.
    * 
    * @param channel
    *           The channel to kick the user from.
    * @param nick
    *           The nick of the user to kick.
    * @param reason
    *           A description of the reason for kicking a user.
    */
   public final void kick(final String channel, final String nick, final String reason) {
	  sendRawLine("KICK " + channel + " " + nick + " :" + reason);
   }

   /**
    * Issues a request for a list of all channels on the IRC server. When the PircBot receives information for each channel, it
    * will call the onChannelInfo method, which you will need to override if you want it to do anything useful.
    * 
    * @see #onChannelInfo(String,int,String) onChannelInfo
    */
   public final void listChannels() {
	  this.listChannels(null);
   }

   /**
    * Issues a request for a list of all channels on the IRC server. When the PircBot receives information for each channel, it
    * will call the onChannelInfo method, which you will need to override if you want it to do anything useful.
    * <p>
    * Some IRC servers support certain parameters for LIST requests. One example is a parameter of ">10" to list only those
    * channels that have more than 10 users in them. Whether these parameters are supported or not will depend on the IRC server
    * software.
    * 
    * @param parameters
    *           The parameters to supply when requesting the list.
    * @see #onChannelInfo(String,int,String) onChannelInfo
    */
   public final void listChannels(final String parameters) {
	  if (parameters == null) {
		 sendRawLine("LIST");
	  } else {
		 sendRawLine("LIST " + parameters);
	  }
   }

   /**
    * Adds a line to the log. This log is currently output to the standard output and is in the correct format for use by tools
    * such as pisg, the Perl IRC Statistics Generator. You may override this method if you wish to do something else with log
    * entries. Each line in the log begins with a number which represents the logging time (as the number of milliseconds since
    * the epoch). This timestamp and the following log entry are separated by a single space character, " ". Outgoing messages are
    * distinguishable by a log entry that has ">>>" immediately following the space character after the timestamp. DCC events use
    * "+++" and warnings about unhandled Exceptions and Errors use "###".
    * <p>
    * This implementation of the method will only cause log entries to be output if the PircBot has had its verbose mode turned on
    * by calling setVerbose(true);
    * 
    * @param line
    *           The line to add to the log.
    */
   public void log(final String line) {
	  if (_verbose) {
		 System.out.println(System.currentTimeMillis() + " " + line);
	  }
   }

   /**
    * A convenient method that accepts an IP address represented as a long and returns an integer array of size 4 representing the
    * same IP address.
    * 
    * @since PircBot 0.9.4
    * @param address
    *           the long value representing the IP address.
    * @return An int[] of size 4.
    */
   public int[] longToIp(final long addressin) {
	  long address = addressin;
	  final int[] ip = new int[4];
	  for (int i = 3; i >= 0; i--) {
		 ip[i] = (int) (address % 256);
		 address = address / 256;
	  }
	  return ip;
   }

   /**
    * This method is called whenever an ACTION is sent from a user. E.g. such events generated by typing "/me goes shopping" in
    * most IRC clients.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @param sender
    *           The nick of the user that sent the action.
    * @param login
    *           The login of the user that sent the action.
    * @param hostname
    *           The hostname of the user that sent the action.
    * @param target
    *           The target of the action, be it a channel or our nick.
    * @param action
    *           The action carried out by the user.
    */
   protected void onAction(final String sender, final String login, final String hostname, final String target,
		 final String action) {
   }

   /**
    * After calling the listChannels() method in PircBot, the server will start to send us information about each channel on the
    * server. You may override this method in order to receive the information about each channel as soon as it is received.
    * <p>
    * Note that certain channels, such as those marked as hidden, may not appear in channel listings.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @param channel
    *           The name of the channel.
    * @param userCount
    *           The number of users visible in this channel.
    * @param topic
    *           The topic for this channel.
    * @see #listChannels() listChannels
    */
   protected void onChannelInfo(final String channel, final int userCount, final String topic) {
   }

   /**
    * This method is called once the PircBot has successfully connected to the IRC server.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.6
    */
   protected void onConnect() {
   }

   /**
    * Called when a user (possibly us) gets operator status taken away.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    * @param recipient
    *           The nick of the user that got 'deopped'.
    */
   protected void onDeHop(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
   }

   /**
    * Called when a user (possibly us) gets half-operator status taken away.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    * @param recipient
    *           The nick of the user that got 'deopped'.
    */
   protected void onDeop(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
   }

   /**
    * Called when a user (possibly us) gets owner status taken away.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    * @param recipient
    *           The nick of the user that got 'deopped'.
    */
   protected void onDeOwner(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
   }

   /**
    * Called when a user (possibly us) gets super-operator status taken away.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    * @param recipient
    *           The nick of the user that got 'deopped'.
    */
   protected void onDeSop(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
   }

   /**
    * Called when a user (possibly us) gets voice status removed.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    * @param recipient
    *           The nick of the user that got 'devoiced'.
    */
   protected void onDeVoice(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
   }

   /**
    * This method carries out the actions to be performed when the PircBot gets disconnected. This may happen if the PircBot quits
    * from the server, or if the connection is unexpectedly lost.
    * <p>
    * Disconnection from the IRC server is detected immediately if either we or the server close the connection normally. If the
    * connection to the server is lost, but neither we nor the server have explicitly closed the connection, then it may take a
    * few minutes to detect (this is commonly referred to as a "ping timeout").
    * <p>
    * If you wish to get your IRC bot to automatically rejoin a server after the connection has been lost, then this is probably
    * the ideal method to override to implement such functionality.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    */
   protected void onDisconnect() {
   }

   /**
    * This method gets called when a DccFileTransfer has finished. If there was a problem, the Exception will say what went wrong.
    * If the file was sent successfully, the Exception will be null.
    * <p>
    * Both incoming and outgoing file transfers are passed to this method. You can determine the type by calling the isIncoming or
    * isOutgoing methods on the DccFileTransfer object.
    * 
    * @since PircBot 1.2.0
    * @param transfer
    *           The DccFileTransfer that has finished.
    * @param e
    *           null if the file was transfered successfully, otherwise this will report what went wrong.
    * @see DccFileTransfer
    */
   protected void onFileTransferFinished(final DccFileTransfer transfer, final Exception e) {
   }

   /**
    * This method is called whenever we receive a FINGER request.
    * <p>
    * This implementation responds correctly, so if you override this method, be sure to either mimic its functionality or to call
    * super.onFinger(...);
    * 
    * @param sourceNick
    *           The nick of the user that sent the FINGER request.
    * @param sourceLogin
    *           The login of the user that sent the FINGER request.
    * @param sourceHostname
    *           The hostname of the user that sent the FINGER request.
    * @param target
    *           The target of the FINGER request, be it our nick or a channel name.
    */
   protected void onFinger(final String sourceNick, final String sourceLogin, final String sourceHostname, final String target) {
	  sendRawLine("NOTICE " + sourceNick + " :\u0001FINGER " + _finger + "\u0001");
   }

   /**
    * Called when a user (possibly us) gets granted operator status for a channel.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    * @param recipient
    *           The nick of the user that got 'opped'.
    */
   protected void onHop(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
   }

   /**
    * This method will be called whenever a DCC Chat request is received. This means that a client has requested to chat to us
    * directly rather than via the IRC server. This is useful for sending many lines of text to and from the bot without having to
    * worry about flooding the server or any operators of the server being able to "spy" on what is being said. This
    * implementation performs no action, which means that all DCC CHAT requests will be ignored by default.
    * <p>
    * If you wish to accept the connection, then you may override this method and call the accept() method on the DccChat object,
    * which connects to the sender of the chat request and allows lines to be sent to and from the bot.
    * <p>
    * Your bot must be able to connect directly to the user that sent the request.
    * <p>
    * Example:
    * 
    * <pre>
    * public void onIncomingChatRequest(DccChat chat) {
    *    try {
    * 	  // Accept all chat, whoever it's from.
    * 	  chat.accept();
    * 	  chat.sendLine(&quot;Hello&quot;);
    * 	  String response = chat.readLine();
    * 	  chat.close();
    *    } catch (IOException e) {
    *    }
    * }
    * </pre>
    * 
    * Each time this method is called, it is called from within a new Thread so that multiple DCC CHAT sessions can run
    * concurrently.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 1.2.0
    * @param chat
    *           A DccChat object that represents the incoming chat request.
    * @see DccChat
    */
   protected void onIncomingChatRequest(final DccChat chat) {
   }

   /**
    * This method is called whenever a DCC SEND request is sent to the PircBot. This means that a client has requested to send a
    * file to us. This implementation performs no action, which means that all DCC SEND requests will be ignored by default. If
    * you wish to receive the file, then you may override this method and call the receive method on the DccFileTransfer object,
    * which connects to the sender and downloads the file.
    * <p>
    * Example:
    * 
    * <pre>
    * public void onIncomingFileTransfer(DccFileTransfer transfer) {
    *    // Use the suggested file name.
    *    File file = transfer.getFile();
    *    // Receive the transfer and save it to the file, allowing resuming.
    *    transfer.receive(file, true);
    * }
    * </pre>
    * <p>
    * <b>Warning:</b> Receiving an incoming file transfer will cause a file to be written to disk. Please ensure that you make
    * adequate security checks so that this file does not overwrite anything important!
    * <p>
    * Each time a file is received, it happens within a new Thread in order to allow multiple files to be downloaded by the
    * PircBot at the same time.
    * <p>
    * If you allow resuming and the file already partly exists, it will be appended to instead of overwritten. If resuming is not
    * enabled, the file will be overwritten if it already exists.
    * <p>
    * You can throttle the speed of the transfer by calling the setPacketDelay method on the DccFileTransfer object, either before
    * you receive the file or at any moment during the transfer.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 1.2.0
    * @param transfer
    *           The DcccFileTransfer that you may accept.
    * @see DccFileTransfer
    */
   protected void onIncomingFileTransfer(final DccFileTransfer transfer) {
   }

   /**
    * Called when we are invited to a channel by a user.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param targetNick
    *           The nick of the user being invited - should be us!
    * @param sourceNick
    *           The nick of the user that sent the invitation.
    * @param sourceLogin
    *           The login of the user that sent the invitation.
    * @param sourceHostname
    *           The hostname of the user that sent the invitation.
    * @param channel
    *           The channel that we're being invited to.
    */
   protected void onInvite(final String targetNick, final String sourceNick, final String sourceLogin,
		 final String sourceHostname, final String channel) {
   }

   /**
    * This method is called whenever someone (possibly us) joins a channel which we are on.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @param channel
    *           The channel which somebody joined.
    * @param sender
    *           The nick of the user who joined the channel.
    * @param login
    *           The login of the user who joined the channel.
    * @param hostname
    *           The hostname of the user who joined the channel.
    */
   protected void onJoin(final String channel, final String sender, final String login, final String hostname) {
   }

   /**
    * This method is called whenever someone (possibly us) is kicked from any of the channels that we are in.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @param channel
    *           The channel from which the recipient was kicked.
    * @param kickerNick
    *           The nick of the user who performed the kick.
    * @param kickerLogin
    *           The login of the user who performed the kick.
    * @param kickerHostname
    *           The hostname of the user who performed the kick.
    * @param recipientNick
    *           The unfortunate recipient of the kick.
    * @param reason
    *           The reason given by the user who performed the kick.
    */
   protected void onKick(final String channel, final String kickerNick, final String kickerLogin, final String kickerHostname,
		 final String recipientNick, final String reason) {
   }

   /**
    * This method is called whenever a message is sent to a channel.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @param channel
    *           The channel to which the message was sent.
    * @param sender
    *           The nick of the person who sent the message.
    * @param login
    *           The login of the person who sent the message.
    * @param hostname
    *           The hostname of the person who sent the message.
    * @param message
    *           The actual message sent to the channel.
    */
   protected void onMessage(final String channel, final String sender, final String handler, final String message) {
   }

   /**
    * Called when the mode of a channel is set.
    * <p>
    * You may find it more convenient to decode the meaning of the mode string by overriding the onOp, onDeOp, onVoice, onDeVoice,
    * onChannelKey, onDeChannelKey, onChannelLimit, onDeChannelLimit, onChannelBan or onDeChannelBan methods as appropriate.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @param channel
    *           The channel that the mode operation applies to.
    * @param sourceNick
    *           The nick of the user that set the mode.
    * @param sourceLogin
    *           The login of the user that set the mode.
    * @param sourceHostname
    *           The hostname of the user that set the mode.
    * @param mode
    *           The mode that has been set.
    */
   protected void onMode(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String mode) {
   }

   /**
    * This method is called whenever someone (possibly us) changes nick on any of the channels that we are on.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @param oldNick
    *           The old nick.
    * @param login
    *           The login of the user.
    * @param hostname
    *           The hostname of the user.
    * @param newNick
    *           The new nick.
    */
   protected void onNickChange(final String oldNick, final String login, final String hostname, final String newNick) {
   }

   /**
    * This method is called whenever we receive a notice.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @param sourceNick
    *           The nick of the user that sent the notice.
    * @param sourceLogin
    *           The login of the user that sent the notice.
    * @param sourceHostname
    *           The hostname of the user that sent the notice.
    * @param target
    *           The target of the notice, be it our nick or a channel name.
    * @param notice
    *           The notice message.
    */
   protected void onNotice(final String sourceNick, final String sourceLogin, final String sourceHostname, final String target,
		 final String notice) {
   }

   /**
    * Called when a user (possibly us) gets granted half-operator status for a channel.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    * @param recipient
    *           The nick of the user that got 'opped'.
    */
   protected void onOp(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
   }

   /**
    * Called when a user (possibly us) gets granted owner status for a channel.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    * @param recipient
    *           The nick of the user that got 'opped'.
    */
   protected void onOwner(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
   }

   /**
    * This method is called whenever someone (possibly us) parts a channel which we are on.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @param channel
    *           The channel which somebody parted from.
    * @param sender
    *           The nick of the user who parted from the channel.
    * @param login
    *           The login of the user who parted from the channel.
    * @param hostname
    *           The hostname of the user who parted from the channel.
    */
   protected void onPart(final String channel, final String sender, final String login, final String hostname) {
   }

   /**
    * This method is called whenever we receive a PING request from another user.
    * <p>
    * This implementation responds correctly, so if you override this method, be sure to either mimic its functionality or to call
    * super.onPing(...);
    * 
    * @param sourceNick
    *           The nick of the user that sent the PING request.
    * @param sourceLogin
    *           The login of the user that sent the PING request.
    * @param sourceHostname
    *           The hostname of the user that sent the PING request.
    * @param target
    *           The target of the PING request, be it our nick or a channel name.
    * @param pingValue
    *           The value that was supplied as an argument to the PING command.
    */
   protected void onPing(final String sourceNick, final String sourceLogin, final String sourceHostname, final String target,
		 final String pingValue) {
	  sendRawLine("NOTICE " + sourceNick + " :\u0001PING " + pingValue + "\u0001");
   }

   /**
    * This method is called whenever a private message is sent to the PircBot.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @param sender
    *           The nick of the person who sent the private message.
    * @param login
    *           The login of the person who sent the private message.
    * @param hostname
    *           The hostname of the person who sent the private message.
    * @param message
    *           The actual message.
    */
   protected void onPrivateMessage(final String sender, final String handler, final String message) {
   }

   /**
    * This method is called whenever someone (possibly us) quits from the server. We will only observe this if the user was in one
    * of the channels to which we are connected.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @param sourceNick
    *           The nick of the user that quit from the server.
    * @param sourceLogin
    *           The login of the user that quit from the server.
    * @param sourceHostname
    *           The hostname of the user that quit from the server.
    * @param reason
    *           The reason given for quitting the server.
    */
   protected void onQuit(final String sourceNick, final String sourceLogin, final String sourceHostname, final String reason) {
   }

   /**
    * Called when a hostmask ban is removed from a channel.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    * @param hostmask
    */
   protected void onRemoveChannelBan(final String channel, final String sourceNick, final String sourceLogin,
		 final String sourceHostname, final String hostmask) {
   }

   /**
    * Called when a channel key is removed.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    * @param key
    *           The key that was in use before the channel key was removed.
    */
   protected void onRemoveChannelKey(final String channel, final String sourceNick, final String sourceLogin,
		 final String sourceHostname, final String key) {
   }

   /**
    * Called when the user limit is removed for a channel.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    */
   protected void onRemoveChannelLimit(final String channel, final String sourceNick, final String sourceLogin,
		 final String sourceHostname) {
   }

   /**
    * Called when a channel has 'invite only' removed.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    */
   protected void onRemoveInviteOnly(final String channel, final String sourceNick, final String sourceLogin,
		 final String sourceHostname) {
   }

   /**
    * Called when a channel has moderated mode removed.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    */
   protected void onRemoveModerated(final String channel, final String sourceNick, final String sourceLogin,
		 final String sourceHostname) {
   }

   /**
    * Called when a channel is set to allow messages from any user, even if they are not actually in the channel.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    */
   protected void onRemoveNoExternalMessages(final String channel, final String sourceNick, final String sourceLogin,
		 final String sourceHostname) {
   }

   /**
    * Called when a channel is marked as not being in private mode.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    */
   protected void onRemovePrivate(final String channel, final String sourceNick, final String sourceLogin,
		 final String sourceHostname) {
   }

   /**
    * Called when a channel has 'secret' mode removed.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    */
   protected void onRemoveSecret(final String channel, final String sourceNick, final String sourceLogin,
		 final String sourceHostname) {
   }

   /**
    * Called when topic protection is removed for a channel.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    */
   protected void onRemoveTopicProtection(final String channel, final String sourceNick, final String sourceLogin,
		 final String sourceHostname) {
   }

   /**
    * The actions to perform when a PING request comes from the server.
    * <p>
    * This sends back a correct response, so if you override this method, be sure to either mimic its functionality or to call
    * super.onServerPing(response);
    * 
    * @param response
    *           The response that should be given back in your PONG.
    */
   protected void onServerPing(final String response) {
	  sendRawLine("PONG " + response);
   }

   /**
    * This method is called when we receive a numeric response from the IRC server.
    * <p>
    * Numerics in the range from 001 to 099 are used for client-server connections only and should never travel between servers.
    * Replies generated in response to commands are found in the range from 200 to 399. Error replies are found in the range from
    * 400 to 599.
    * <p>
    * For example, we can use this method to discover the topic of a channel when we join it. If we join the channel #test which
    * has a topic of &quot;I am King of Test&quot; then the response will be &quot; <code>PircBot #test :I Am King of Test</code>
    * &quot; with a code of 332 to signify that this is a topic. (This is just an example - note that overriding the
    * <code>onTopic</code> method is an easier way of finding the topic for a channel). Check the IRC RFC for the full list of
    * other command response codes.
    * <p>
    * PircBot implements the interface ReplyConstants, which contains contstants that you may find useful here.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @param code
    *           The three-digit numerical code for the response.
    * @param response
    *           The full response from the IRC server.
    * @see ReplyConstants
    */
   protected void onServerResponse(final int code, final String response, final String channel) {
   }

   /**
    * Called when a user (possibly us) gets banned from a channel. Being banned from a channel prevents any user with a matching
    * hostmask from joining the channel. For this reason, most bans are usually directly followed by the user being kicked :-)
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    * @param hostmask
    *           The hostmask of the user that has been banned.
    */
   protected void onSetChannelBan(final String channel, final String sourceNick, final String sourceLogin,
		 final String sourceHostname, final String hostmask) {
   }

   /**
    * Called when a channel key is set. When the channel key has been set, other users may only join that channel if they know the
    * key. Channel keys are sometimes referred to as passwords.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    * @param key
    *           The new key for the channel.
    */
   protected void onSetChannelKey(final String channel, final String sourceNick, final String sourceLogin,
		 final String sourceHostname, final String key) {
   }

   /**
    * Called when a user limit is set for a channel. The number of users in the channel cannot exceed this limit.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    * @param limit
    *           The maximum number of users that may be in this channel at the same time.
    */
   protected void onSetChannelLimit(final String channel, final String sourceNick, final String sourceLogin,
		 final String sourceHostname, final int limit) {
   }

   /**
    * Called when a channel is set to 'invite only' mode. A user may only join the channel if they are invited by someone who is
    * already in the channel.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    */
   protected void onSetInviteOnly(final String channel, final String sourceNick, final String sourceLogin,
		 final String sourceHostname) {
   }

   /**
    * Called when a channel is set to 'moderated' mode. If a channel is moderated, then only users who have been 'voiced' or
    * 'opped' may speak or change their nicks.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    */
   protected void onSetModerated(final String channel, final String sourceNick, final String sourceLogin,
		 final String sourceHostname) {
   }

   /**
    * Called when a channel is set to only allow messages from users that are in the channel.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    */
   protected void onSetNoExternalMessages(final String channel, final String sourceNick, final String sourceLogin,
		 final String sourceHostname) {
   }

   /**
    * Called when a channel is marked as being in private mode.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    */
   protected void onSetPrivate(final String channel, final String sourceNick, final String sourceLogin,
		 final String sourceHostname) {
   }

   /**
    * Called when a channel is set to be in 'secret' mode. Such channels typically do not appear on a server's channel listing.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    */
   protected void onSetSecret(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname) {
   }

   /**
    * Called when topic protection is enabled for a channel. Topic protection means that only operators in a channel may change
    * the topic.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    */
   protected void onSetTopicProtection(final String channel, final String sourceNick, final String sourceLogin,
		 final String sourceHostname) {
   }

   /**
    * Called when a user (possibly us) gets granted super-operator status for a channel.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    * @param recipient
    *           The nick of the user that got 'opped'.
    */
   protected void onSop(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
   }

   /**
    * This method is called whenever we receive a TIME request.
    * <p>
    * This implementation responds correctly, so if you override this method, be sure to either mimic its functionality or to call
    * super.onTime(...);
    * 
    * @param sourceNick
    *           The nick of the user that sent the TIME request.
    * @param sourceLogin
    *           The login of the user that sent the TIME request.
    * @param sourceHostname
    *           The hostname of the user that sent the TIME request.
    * @param target
    *           The target of the TIME request, be it our nick or a channel name.
    */
   protected void onTime(final String sourceNick, final String sourceLogin, final String sourceHostname, final String target) {
	  sendRawLine("NOTICE " + sourceNick + " :\u0001TIME " + new Date().toString() + "\u0001");
   }

   /**
    * This method is called whenever a user sets the topic, or when PircBot joins a new channel and discovers its topic.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @param channel
    *           The channel that the topic belongs to.
    * @param topic
    *           The topic for the channel.
    * @param setBy
    *           The nick of the user that set the topic.
    * @param date
    *           When the topic was set (milliseconds since the epoch).
    * @param changed
    *           True if the topic has just been changed, false if the topic was already there.
    */
   protected void onTopic(final String channel, final String topic, final String setBy, final long date, final boolean changed) {
   }

   /**
    * This method is called whenever we receive a line from the server that the PircBot has not been programmed to recognise.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @param line
    *           The raw line that was received from the server.
    */
   protected void onUnknown(final String line) {
   }

   /**
    * This method is called when we receive a user list from the server after joining a channel.
    * <p>
    * Shortly after joining a channel, the IRC server sends a list of all users in that channel. The PircBot collects this
    * information and calls this method as soon as it has the full list.
    * <p>
    * To obtain the nick of each user in the channel, call the getNick() method on each User object in the array.
    * <p>
    * At a later time, you may call the getUsers method to obtain an up to date list of the users in the channel.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 1.0.0
    * @param channel
    *           The name of the channel.
    * @param users
    *           An array of User objects belonging to this channel.
    * @see User
    */
   protected void onUserList(final String channel, final User[] users) {
   }

   /**
    * Called when the mode of a user is set.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 1.2.0
    * @param targetNick
    *           The nick that the mode operation applies to.
    * @param sourceNick
    *           The nick of the user that set the mode.
    * @param sourceLogin
    *           The login of the user that set the mode.
    * @param sourceHostname
    *           The hostname of the user that set the mode.
    * @param mode
    *           The mode that has been set.
    */
   protected void onUserMode(final String targetNick, final String sourceNick, final String sourceLogin,
		 final String sourceHostname, final String mode) {
   }

   /**
    * This method is called whenever we receive a VERSION request. This implementation responds with the PircBot's _version
    * string, so if you override this method, be sure to either mimic its functionality or to call super.onVersion(...);
    * 
    * @param sourceNick
    *           The nick of the user that sent the VERSION request.
    * @param sourceLogin
    *           The login of the user that sent the VERSION request.
    * @param sourceHostname
    *           The hostname of the user that sent the VERSION request.
    * @param target
    *           The target of the VERSION request, be it our nick or a channel name.
    */
   protected void onVersion(final String sourceNick, final String sourceLogin, final String sourceHostname, final String target) {
	  sendRawLine("NOTICE " + sourceNick + " :\u0001VERSION " + _version + "\u0001");
   }

   /**
    * Called when a user (possibly us) gets voice status granted in a channel.
    * <p>
    * This is a type of mode change and is also passed to the onMode method in the PircBot class.
    * <p>
    * The implementation of this method in the PircBot class performs no actions and may be overridden as required.
    * 
    * @since PircBot 0.9.5
    * @param channel
    *           The channel in which the mode change took place.
    * @param sourceNick
    *           The nick of the user that performed the mode change.
    * @param sourceLogin
    *           The login of the user that performed the mode change.
    * @param sourceHostname
    *           The hostname of the user that performed the mode change.
    * @param recipient
    *           The nick of the user that got 'voiced'.
    */
   protected void onVoice(final String channel, final String sourceNick, final String sourceLogin, final String sourceHostname,
		 final String recipient) {
   }

   /**
    * Grants operator privilidges to a user on a channel. Successful use of this method may require the bot to have operator
    * status itself.
    * 
    * @param channel
    *           The channel we're opping the user on.
    * @param nick
    *           The nick of the user we are opping.
    */
   public final void op(final String channel, final String nick) {
	  setMode(channel, "+o " + nick);
   }

   /**
    * Parts a channel.
    * 
    * @param channel
    *           The name of the channel to leave.
    */
   public final void partChannel(final String channel) {
	  sendRawLine("PART " + channel);
   }

   /**
    * Parts a channel, giving a reason.
    * 
    * @param channel
    *           The name of the channel to leave.
    * @param reason
    *           The reason for parting the channel.
    */
   public final void partChannel(final String channel, final String reason) {
	  sendRawLine("PART " + channel + " :" + reason);
   }

   /**
    * Called when the mode of a channel is set. We process this in order to call the appropriate onOp, onDeop, etc method before
    * finally calling the override-able onMode method.
    * <p>
    * Note that this method is private and is not intended to appear in the javadoc generated documentation.
    * 
    * @param target
    *           The channel or nick that the mode operation applies to.
    * @param sourceNick
    *           The nick of the user that set the mode.
    * @param sourceLogin
    *           The login of the user that set the mode.
    * @param sourceHostname
    *           The hostname of the user that set the mode.
    * @param mode
    *           The mode that has been set.
    */
   private final void processMode(final String target, final String sourceNick, final String sourceLogin,
		 final String sourceHostname, final String mode) {

	  if (_channelPrefixes.indexOf(target.charAt(0)) >= 0) {
		 // The mode of a channel is being changed.
		 final String channel = target;
		 final StringTokenizer tok = new StringTokenizer(mode);
		 final String[] params = new String[tok.countTokens()];

		 int t = 0;
		 while (tok.hasMoreTokens()) {
			params[t] = tok.nextToken();
			t++;
		 }

		 char pn = ' ';
		 int p = 1;

		 // All of this is very large and ugly, but it's the only way of
		 // providing
		 // what the users want :-/
		 for (int i = 0; i < params[0].length(); i++) {
			final char atPos = params[0].charAt(i);

			if (atPos == '+' || atPos == '-') {
			   pn = atPos;
			} else if (atPos == 'q') {
			   if (pn == '+') {
				  onOwner(channel, sourceNick, sourceLogin, sourceHostname, params[p]);
			   } else {
				  onDeOwner(channel, sourceNick, sourceLogin, sourceHostname, params[p]);
			   }
			   p++;
			} else if (atPos == 'a') {
			   if (pn == '+') {
				  onSop(channel, sourceNick, sourceLogin, sourceHostname, params[p]);
			   } else {
				  onDeSop(channel, sourceNick, sourceLogin, sourceHostname, params[p]);
			   }
			   p++;
			} else if (atPos == 'o') {
			   if (pn == '+') {
				  updateUser(channel, OP_ADD, params[p]);
				  onOp(channel, sourceNick, sourceLogin, sourceHostname, params[p]);
			   } else {
				  updateUser(channel, OP_REMOVE, params[p]);
				  onDeop(channel, sourceNick, sourceLogin, sourceHostname, params[p]);
			   }
			   p++;
			} else if (atPos == 'h') {
			   if (pn == '+') {
				  onHop(channel, sourceNick, sourceLogin, sourceHostname, params[p]);
			   } else {
				  onDeHop(channel, sourceNick, sourceLogin, sourceHostname, params[p]);
			   }
			   p++;
			} else if (atPos == 'v') {
			   if (pn == '+') {
				  updateUser(channel, VOICE_ADD, params[p]);
				  onVoice(channel, sourceNick, sourceLogin, sourceHostname, params[p]);
			   } else {
				  updateUser(channel, VOICE_REMOVE, params[p]);
				  onDeVoice(channel, sourceNick, sourceLogin, sourceHostname, params[p]);
			   }
			   p++;
			} else if (atPos == 'k') {
			   if (pn == '+') {
				  onSetChannelKey(channel, sourceNick, sourceLogin, sourceHostname, params[p]);
			   } else {
				  onRemoveChannelKey(channel, sourceNick, sourceLogin, sourceHostname, params[p]);
			   }
			   p++;
			} else if (atPos == 'l') {
			   if (pn == '+') {
				  onSetChannelLimit(channel, sourceNick, sourceLogin, sourceHostname, Integer.parseInt(params[p]));
				  p++;
			   } else {
				  onRemoveChannelLimit(channel, sourceNick, sourceLogin, sourceHostname);
			   }
			} else if (atPos == 'b') {
			   if (pn == '+') {
				  onSetChannelBan(channel, sourceNick, sourceLogin, sourceHostname, params[p]);
			   } else {
				  onRemoveChannelBan(channel, sourceNick, sourceLogin, sourceHostname, params[p]);
			   }
			   p++;
			} else if (atPos == 't') {
			   if (pn == '+') {
				  onSetTopicProtection(channel, sourceNick, sourceLogin, sourceHostname);
			   } else {
				  onRemoveTopicProtection(channel, sourceNick, sourceLogin, sourceHostname);
			   }
			} else if (atPos == 'n') {
			   if (pn == '+') {
				  onSetNoExternalMessages(channel, sourceNick, sourceLogin, sourceHostname);
			   } else {
				  onRemoveNoExternalMessages(channel, sourceNick, sourceLogin, sourceHostname);
			   }
			} else if (atPos == 'i') {
			   if (pn == '+') {
				  onSetInviteOnly(channel, sourceNick, sourceLogin, sourceHostname);
			   } else {
				  onRemoveInviteOnly(channel, sourceNick, sourceLogin, sourceHostname);
			   }
			} else if (atPos == 'm') {
			   if (pn == '+') {
				  onSetModerated(channel, sourceNick, sourceLogin, sourceHostname);
			   } else {
				  onRemoveModerated(channel, sourceNick, sourceLogin, sourceHostname);
			   }
			} else if (atPos == 'p') {
			   if (pn == '+') {
				  onSetPrivate(channel, sourceNick, sourceLogin, sourceHostname);
			   } else {
				  onRemovePrivate(channel, sourceNick, sourceLogin, sourceHostname);
			   }
			} else if (atPos == 's') {
			   if (pn == '+') {
				  onSetSecret(channel, sourceNick, sourceLogin, sourceHostname);
			   } else {
				  onRemoveSecret(channel, sourceNick, sourceLogin, sourceHostname);
			   }
			}
		 }

		 onMode(channel, sourceNick, sourceLogin, sourceHostname, mode);
	  } else {
		 // The mode of a user is being changed.
		 final String nick = target;
		 onUserMode(nick, sourceNick, sourceLogin, sourceHostname, mode);
	  }
   }

   /**
    * This method is called by the PircBot when a numeric response is received from the IRC server. We use this method to allow
    * PircBot to process various responses from the server before then passing them on to the onServerResponse method.
    * <p>
    * Note that this method is private and should not appear in any of the javadoc generated documenation.
    * 
    * @param code
    *           The three-digit numerical code for the response.
    * @param response
    *           The full response from the IRC server.
    */
   private final void processServerResponse(final int code, final String response) {
	  String channel = "";
	  if (code == RPL_LIST) {
		 // This is a bit of information about a channel.
		 final int firstSpace = response.indexOf(' ');
		 final int secondSpace = response.indexOf(' ', firstSpace + 1);
		 final int thirdSpace = response.indexOf(' ', secondSpace + 1);
		 final int colon = response.indexOf(':');
		 channel = response.substring(firstSpace + 1, secondSpace);
		 int userCount = 0;
		 try {
			userCount = Integer.parseInt(response.substring(secondSpace + 1, thirdSpace));
		 } catch (final NumberFormatException e) {
			// Stick with the value of zero.
		 }
		 final String topic = response.substring(colon + 1);
		 onChannelInfo(channel, userCount, topic);
	  } else if (code == RPL_TOPIC) {
		 // This is topic information about a channel we've just joined.
		 final int firstSpace = response.indexOf(' ');
		 final int secondSpace = response.indexOf(' ', firstSpace + 1);
		 final int colon = response.indexOf(':');
		 channel = response.substring(firstSpace + 1, secondSpace);
		 final String topic = response.substring(colon + 1);

		 _topics.put(channel, topic);
	  } else if (code == RPL_TOPICINFO) {
		 final StringTokenizer tokenizer = new StringTokenizer(response);
		 tokenizer.nextToken();
		 channel = tokenizer.nextToken();
		 final String setBy = tokenizer.nextToken();
		 long date = 0;
		 try {
			date = Long.parseLong(tokenizer.nextToken()) * 1000;
		 } catch (final NumberFormatException e) {
			// Stick with the default value of zero.
		 }

		 final String topic = _topics.get(channel);
		 _topics.remove(channel);

		 onTopic(channel, topic, setBy, date, false);
	  } else if (code == RPL_NAMREPLY) {
		 // This is a list of nicks in a channel that we've just joined.
		 final int channelEndIndex = response.indexOf(" :");
		 channel = response.substring(response.lastIndexOf(' ', channelEndIndex - 1) + 1, channelEndIndex);

		 final StringTokenizer tokenizer = new StringTokenizer(response.substring(response.indexOf(" :") + 2));
		 while (tokenizer.hasMoreTokens()) {
			String nick = tokenizer.nextToken();
			String prefix = "";
			if (nick.indexOf('~') != -1) {
			   // User is owner in this channel.
			   prefix = "~";
			} else if (nick.indexOf('&') != -1) {
			   // User is sop in this channel.
			   prefix = "&";
			} else if (nick.indexOf('@') != -1) {
			   // User is an op in this channel.
			   prefix = "@";
			} else if (nick.indexOf('%') != -1) {
			   // User is hop in this channel.
			   prefix = "%";
			} else if (nick.indexOf('+') != -1) {
			   // User is voiced in this channel.
			   prefix = "+";
			}
			nick = nick.replaceAll("[@%&~+]", "");
			addUser(channel, new User(prefix, nick));
		 }
	  } else if (code == RPL_ENDOFNAMES) {
		 // This is the end of a NAMES list, so we know that we've got
		 // the full list of users in the channel that we just joined.
		 channel = response.substring(response.indexOf(' ') + 1, response.indexOf(" :"));
		 final User[] users = getUsers(channel);
		 onUserList(channel, users);
	  }

	  onServerResponse(code, response, channel);
   }

   /**
    * Quits from the IRC server. Providing we are actually connected to an IRC server, the onDisconnect() method will be called as
    * soon as the IRC server disconnects us.
    */
   public final void quitServer() {
	  this.quitServer("");
   }

   /**
    * Quits from the IRC server with a reason. Providing we are actually connected to an IRC server, the onDisconnect() method
    * will be called as soon as the IRC server disconnects us.
    * 
    * @param reason
    *           The reason for quitting the server.
    */
   public final void quitServer(final String reason) {
	  sendRawLine("QUIT :" + reason);
   }

   /**
    * Reconnects to the IRC server that we were previously connected to. If necessary, the appropriate port number and password
    * will be used. This method will throw an IrcException if we have never connected to an IRC server previously.
    * 
    * @since PircBot 0.9.9
    * @throws IOException
    *            if it was not possible to connect to the server.
    * @throws IrcException
    *            if the server would not let us join it.
    * @throws NickAlreadyInUseException
    *            if our nick is already in use on the server.
    * @throws AlreadyConnectedException
    */
   public final void reconnect() throws IOException, IrcException, NickAlreadyInUseException, AlreadyConnectedException {
	  synchronized (this) {
		 if (getServer() == null)
			throw new IrcException("Cannot reconnect to an IRC server because we were never connected to one previously!");
		 connect(getServer(), getPort(), getPassword());
	  }
   }

   /**
    * Removes all channels from our memory of users.
    */
   private final void removeAllChannels() {
	  synchronized (this) {
		 _channels = new Hashtable<String, Hashtable<User, User>>();
	  }
   }

   /**
    * Removes an entire channel from our memory of users.
    */
   private final void removeChannel(final String channel) {
	  synchronized (_channels) {
		 _channels.remove(channel.toLowerCase());
	  }
   }

   /**
    * Remove a user from all channels in our memory.
    */
   private final void removeUser(final String nick) {
	  synchronized (_channels) {
		 final Enumeration<String> enumeration = _channels.keys();
		 while (enumeration.hasMoreElements()) {
			final String channel = enumeration.nextElement();
			this.removeUser(channel, nick);
		 }
	  }
   }

   /**
    * Remove a user from the specified channel in our memory.
    */
   private final User removeUser(final String channel, final String nick) {
	  final User user = new User("", nick);
	  synchronized (_channels) {
		 final Hashtable<User, User> users = _channels.get(channel.toLowerCase());
		 if (users != null)
			return users.remove(user);
	  }
	  return null;
   }

   /**
    * Rename a user if they appear in any of the channels we know about.
    */
   private final void renameUser(final String oldNick, final String newNick) {
	  synchronized (_channels) {
		 final Enumeration<String> enumeration = _channels.keys();
		 while (enumeration.hasMoreElements()) {
			final String channel = enumeration.nextElement();
			User user = this.removeUser(channel, oldNick);
			if (user != null) {
			   user = new User(user.getPrefix(), newNick);
			   addUser(channel, user);
			}
		 }
	  }
   }

   /**
    * Sends an action to the channel or to a user.
    * 
    * @param target
    *           The name of the channel or user nick to send to.
    * @param action
    *           The action to send.
    * @see Colors
    */
   public final void sendAction(final String target, final String action) {
	  sendMessage(target, "\u0001ACTION " + action + "\u0001");
   }

   /**
    * Sends a CTCP command to a channel or user. (Client to client protocol). Examples of such commands are "PING <number>",
    * "FINGER", "VERSION", etc. For example, if you wish to request the version of a user called "Dave", then you would call
    * <code>sendCTCPCommand("Dave", "VERSION");</code>. The type of response to such commands is largely dependant on the target
    * client software.
    * 
    * @since PircBot 0.9.5
    * @param target
    *           The name of the channel or user to send the CTCP message to.
    * @param command
    *           The CTCP command to send.
    */
   public void sendCTCPCommand(final String target, final String command) {
   }

   /**
    * Sends an invitation to join a channel. Some channels can be marked as "invite-only", so it may be useful to allow a bot to
    * invite people into it.
    * 
    * @param nick
    *           The nick of the user to invite
    * @param channel
    *           The channel you are inviting the user to join.
    */
   public final void sendInvite(final String nick, final String channel) {
	  sendRawLine("INVITE " + nick + " :" + channel);
   }

   public void sendMessage(final String target, final String message) {
   }

   public void sendNotice(final String target, final String message) {
   }

   /**
    * Sends a raw line to the IRC server as soon as possible, bypassing the outgoing message queue.
    * 
    * @param line
    *           The raw line to send to the IRC server.
    */
   public final void sendRawLine(final String line) {
	  if (isConnected()) {
		 _inputThread.sendRawLine(line);
	  }
   }

   /**
    * Sends a raw line through the outgoing message queue.
    * 
    * @param line
    *           The raw line to send to the IRC server.
    */
   public void sendRawLineViaQueue(final String sender, final String line) {
   }

   /**
    * When you connect to a server and your nick is already in use and this is set to true, a new nick will be automatically
    * chosen. This is done by adding numbers to the end of the nick until an available nick is found.
    * 
    * @param autoNickChange
    *           Set to true if you want automatic nick changes during connection.
    */
   public void setAutoNickChange(final boolean autoNickChange) {
	  _autoNickChange = autoNickChange;
   }

   /**
    * Sets the InetAddress to be used when sending DCC chat or file transfers. This can be very useful when you are running a bot
    * on a machine which is behind a firewall and you need to tell receiving clients to connect to a NAT/router, which then
    * forwards the connection.
    * 
    * @since PircBot 1.4.4
    * @param dccInetAddress
    *           The new InetAddress, or null to use the default.
    */
   public void setDccInetAddress(final InetAddress dccInetAddress) {
	  _dccInetAddress = dccInetAddress;
   }

   /**
    * Sets the choice of port numbers that can be used when sending a DCC chat or file transfer. This is useful when you are
    * behind a firewall and need to set up port forwarding. The array of port numbers is traversed in sequence until a free port
    * is found to listen on. A DCC tranfer will fail if all ports are already in use. If set to null, <i>any</i> free port number
    * will be used.
    * 
    * @since PircBot 1.4.4
    * @param ports
    *           The set of port numbers that PircBot may use for DCC transfers, or null to let it use any free port (default).
    */
   public void setDccPorts(final int[] ports) {
	  if (ports == null || ports.length == 0) {
		 _dccPorts = null;
	  } else {
		 // Clone the array to prevent external modification.
		 _dccPorts = ports.clone();
	  }
   }

   /**
    * Sets the encoding charset to be used when sending or receiving lines from the IRC server. If set to null, then the
    * platform's default charset is used. You should only use this method if you are trying to send text to an IRC server in a
    * different charset, e.g. "GB2312" for Chinese encoding. If a PircBot is currently connected to a server, then it must
    * reconnect before this change takes effect.
    * 
    * @since PircBot 1.0.4
    * @param charset
    *           The new encoding charset to be used by PircBot.
    * @throws UnsupportedEncodingException
    *            If the named charset is not supported.
    */
   public void setEncoding(final String charset) throws UnsupportedEncodingException {
	  // Just try to see if the charset is supported first...
	  "".getBytes(charset);

	  _charset = charset;
   }

   /**
    * Sets the interal finger message. This should be set before joining any servers.
    * 
    * @param finger
    *           The new finger message for the Bot.
    */
   protected final void setFinger(final String finger) {
	  _finger = finger;
   }

   /**
    * Sets the internal login of the Bot. This should be set before joining any servers.
    * 
    * @param login
    *           The new login of the Bot.
    */
   protected final void setLogin(final String login) {
	  _login = login;
   }

   /**
    * Set the mode of a channel. This method attempts to set the mode of a channel. This may require the bot to have operator
    * status on the channel. For example, if the bot has operator status, we can grant operator status to "Dave" on the #cs
    * channel by calling setMode("#cs", "+o Dave"); An alternative way of doing this would be to use the op method.
    * 
    * @param channel
    *           The channel on which to perform the mode change.
    * @param mode
    *           The new mode to apply to the channel. This may include zero or more arguments if necessary.
    * @see #op(String,String) op
    */
   public final void setMode(final String channel, final String mode) {
	  sendRawLine("MODE " + channel + " " + mode);
   }

   /**
    * Sets the name of the bot, which will be used as its nick when it tries to join an IRC server. This should be set before
    * joining any servers, otherwise the default nick will be used. You would typically call this method from the constructor of
    * the class that extends PircBot.
    * <p>
    * The changeNick method should be used if you wish to change your nick when you are connected to a server.
    * 
    * @param name
    *           The new name of the Bot.
    */
   protected final void setName(final String name) {
	  _name = name;
   }

   /**
    * Sets the internal nick of the bot. This is only to be called by the PircBot class in response to notification of nick
    * changes that apply to us.
    * 
    * @param nick
    *           The new nick.
    */
   private final void setNick(final String nick) {
	  _nick = nick;
   }

   /**
    * Set the topic for a channel. This method attempts to set the topic of a channel. This may require the bot to have operator
    * status if the topic is protected.
    * 
    * @param channel
    *           The channel on which to perform the mode change.
    * @param topic
    *           The new topic for the channel.
    */
   public final void setTopic(final String channel, final String topic) {
	  sendRawLine("TOPIC " + channel + " :" + topic);
   }

   /**
    * Sets the verbose mode. If verbose mode is set to true, then log entries will be printed to the standard output. The default
    * value is false and will result in no output. For general development, we strongly recommend setting the verbose mode to
    * true.
    * 
    * @param verbose
    *           true if verbose mode is to be used. Default is false.
    */
   public final void setVerbose(final boolean verbose) {
	  _verbose = verbose;
   }

   /**
    * Sets the internal version of the Bot. This should be set before joining any servers.
    * 
    * @param version
    *           The new version of the Bot.
    */
   protected final void setVersion(final String version) {
	  _version = version;
   }

   /**
    * Starts an ident server (Identification Protocol Server, RFC 1413).
    * <p>
    * Most IRC servers attempt to contact the ident server on connecting hosts in order to determine the user's identity. A few
    * IRC servers will not allow you to connect unless this information is provided.
    * <p>
    * So when a PircBot is run on a machine that does not run an ident server, it may be necessary to call this method to start
    * one up.
    * <p>
    * Calling this method starts up an ident server which will respond with the login provided by calling getLogin() and then shut
    * down immediately. It will also be shut down if it has not been contacted within 60 seconds of creation.
    * <p>
    * If you require an ident response, then the correct procedure is to start the ident server and then connect to the IRC
    * server. The IRC server may then contact the ident server to get the information it needs.
    * <p>
    * The ident server will fail to start if there is already an ident server running on port 113, or if you are running as an
    * unprivileged user who is unable to create a server socket on that port number.
    * <p>
    * If it is essential for you to use an ident server when connecting to an IRC server, then make sure that port 113 on your
    * machine is visible to the IRC server so that it may contact the ident server.
    * 
    * @since PircBot 0.9c
    */
   public final void startIdentServer() {
	  new IdentServer(this, getLogin());
   }

   /**
    * Returns a String representation of this object. You may find this useful for debugging purposes, particularly if you are
    * using more than one PircBot instance to achieve multiple server connectivity. The format of this String may change between
    * different versions of PircBot but is currently something of the form <code>
    *   Version{PircBot x.y.z Java IRC Bot - www.jibble.org}
    *   Connected{true}
    *   Server{irc.dal.net}
    *   Port{6667}
    *   Password{}
    * </code>
    * 
    * @since PircBot 0.9.10
    * @return a String representation of this object.
    */

   @Override
   public String toString() {
	  return "Version{" + _version + "}" + " Connected{" + isConnected() + "}" + " Server{" + _server + "}" + " Port{" + _port
			+ "}" + " Password{" + _password + "}";
   }

   /**
    * Unbans a user from a channel. An example of a valid hostmask is "*!*compu@*.18hp.net". Successful use of this method may
    * require the bot to have operator status itself.
    * 
    * @param channel
    *           The channel to unban the user from.
    * @param hostmask
    *           A hostmask representing the user we're unbanning.
    */
   public final void unBan(final String channel, final String hostmask) {
	  sendRawLine("MODE " + channel + " -b " + hostmask);
   }

   private final void updateUser(final String channel, final int userMode, final String nick) {
	  synchronized (_channels) {
		 final Hashtable<User, User> users = _channels.get(channel.toLowerCase());
		 User newUser = null;
		 if (users != null) {
			final Enumeration<User> enumeration = users.elements();
			while (enumeration.hasMoreElements()) {
			   final User userObj = enumeration.nextElement();
			   if (userObj.getNick().equalsIgnoreCase(nick)) {
				  if (userMode == OP_ADD) {
					 if (userObj.hasVoice()) {
						newUser = new User("@+", nick);
					 } else {
						newUser = new User("@", nick);
					 }
				  } else if (userMode == OP_REMOVE) {
					 if (userObj.hasVoice()) {
						newUser = new User("+", nick);
					 } else {
						newUser = new User("", nick);
					 }
				  } else if (userMode == VOICE_ADD) {
					 if (userObj.isOp()) {
						newUser = new User("@+", nick);
					 } else {
						newUser = new User("+", nick);
					 }
				  } else if (userMode == VOICE_REMOVE) {
					 if (userObj.isOp()) {
						newUser = new User("@", nick);
					 } else {
						newUser = new User("", nick);
					 }
				  }
			   }
			}
		 }
		 if (newUser == null) {
			// just in case ...
			newUser = new User("", nick);
			users.put(newUser, newUser);
		 } else {
			users.put(newUser, newUser);
		 }
	  }
   }

   /**
    * Grants voice privilidges to a user on a channel. Successful use of this method may require the bot to have operator status
    * itself.
    * 
    * @param channel
    *           The channel we're voicing the user on.
    * @param nick
    *           The nick of the user we are voicing.
    */
   public final void voice(final String channel, final String nick) {
	  setMode(channel, "+v " + nick);
   }
}
